package routers

import (
	"context"
	"fmt"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"os"
	"tapay-ws/global"
	"tapay-ws/internal/middleware"
	"tapay-ws/internal/model"
	"tapay-ws/internal/routers/api/v1"
	"tapay-ws/pkg/app"
	"tapay-ws/pkg/errcode"
	"tapay-ws/pkg/limiter"
	"tapay-ws/pkg/redis"
	"tapay-ws/pkg/service"
	"tapay-ws/pkg/util/xstring"
	"time"
)

var methodLimiters = limiter.NewMethodLimiter().AddBuckets(
	limiter.LimiterBucketRule{
		Key:          "/debug/vars",
		FillInterval: time.Second,
		Capacity:     10000,
		Quantum:      5000,
	},
)

var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func NewRouter() *gin.Engine {
	r := gin.New()
	c := cors.Config{
		AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},
		AllowHeaders:     []string{"Origin", "Content-Length", "Content-Type", "token"},
		AllowCredentials: false,
		MaxAge:           12 * time.Hour,
	}
	c.AllowAllOrigins = true
	r.Use(cors.New(c))

	if global.ServerSetting.RunMode == "debug" {
		r.Use(gin.Logger())
	} else {
		file, _ := os.OpenFile(global.AppSetting.LogSavePath+"/gin.log", os.O_CREATE|os.O_WRONLY, 0666)
		r.Use(gin.LoggerWithConfig(gin.LoggerConfig{Output: file}))
	}
	r.Use(gin.Recovery())
	r.GET("ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello, this is a WebSocket example with Gin!",
		})
	})
	userConn := v1.NewUserConn()
	appToken := v1.NewAppToken()
	//前端接口
	apiWs := r.Group("/")
	apiWs.Use(middleware.JWT())
	{
		apiWs.GET("wss", errWrapper(upgradeToWs))

		apiWs.GET("ws", errWrapper(upgradeToWs))
	}

	apiV1Authed := r.Group("/api/v1")
	apiV1Authed.Use(middleware.JWT())
	apiV1Authed.Use(middleware.ContextTimeout(global.AppSetting.DefaultContextTimeout))
	{
		apiV1Authed.GET("/close", func(c *gin.Context) {
			svc := service.New(c.Request.Context())
			conn := svc.GetConn(app.GetUserId(c), app.GetUserType(c))
			if conn != nil {
				conn.Close()
			}
			response := app.NewResponse(c)
			response.ToResponse(nil)
		})

		apiV1Authed.POST("/check-token", errWrapper(appToken.CheckToken))
	}

	apiV1 := r.Group("/api/v1")
	apiV1.Use(middleware.ContextTimeout(global.AppSetting.DefaultContextTimeout))
	{
		//发送消息
		apiV1.POST("/send/msg", errWrapper(userConn.SendMsg))
	}

	return r
}

func upgradeToWs(c *gin.Context) error {
	userId := app.GetUserId(c)
	userType := app.GetUserType(c)
	log.Printf("========================================= request connect uid %d;userType:%d\n", userId, userType)
	return redis.RepeatSubmitLock(fmt.Sprintf("ws_connection_%d_%d", userId, userType), 5*time.Second, func() error {
		// 升级HTTP连接为WebSocket连接
		conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
		if err != nil {
			global.Logger.Errorf(context.Background(), fmt.Sprintf("[%d]upgrade http to WebSocket err:%v", userId, err))
			return err
		}
		svc := service.New(c.Request.Context())
		user, err := svc.AddUserConnList(userId, userType, conn)
		if err != nil {
			global.Logger.Errorf(context.Background(), fmt.Sprintf("[%d]add user Conn List err:%v", userId, err))
			return err
		}
		// 处理 WebSocket 连接
		return handleWebSocket(&svc, user)
	})
}

func handleWebSocket(svc *service.Service, user *service.User) (err error) {
	defer func() {
		err = svc.CloseConn(user, "error")
	}()

	for {
		// 读取WebSocket消息
		_, message, err := user.Conn.ReadMessage()
		if err != nil {
			log.Printf("=========================== uid[%d],Conn Read Message err:%v\n", user.ID, err)
			// Check if the error indicates a closed connection
			if websocket.IsCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Printf("===================== uid[%d],Connection closed by client.\n", user.ID)
			}
			break
		}
		if message != nil {
			// 处理收到的消息
			if string(message) == "ping" {
				//log.Printf("=========================== uid[%d],Conn Write Message pong\n", user.ID)
				err = user.Conn.WriteMessage(websocket.TextMessage, []byte("pong"))
				if err != nil {
					log.Printf("=========================== uid[%d],Conn Write Message err:%v\n", user.ID, err)
					break
				}
			}
		}
	}
	return
}

type appHandlerFunc func(*gin.Context) error

// errWrapper
//
//	@Author achilles
//	@Description: 错误包装函数
//	@param appHandler
//	@param fc
//	@date 2022-04-26 09:21:10
//	@return func(ctx *gin.Context)
func errWrapper(appHandler appHandlerFunc) func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		err := appHandler(ctx)
		if err == nil {
			return
		}
		var ok bool
		var errCode *errcode.Error
		if errCode, ok = err.(*errcode.Error); !ok {
			errLog := model.ErrorLog{}
			funcName := xstring.FunctionName(appHandler)
			errLog.CreateErrLog(global.DBEngine, app.GetUserId(ctx), fmt.Sprintf("tapay-ws errWrapper:%s", funcName), nil, err)
			if errCode == nil {
				errCode = errcode.ErrorSystemFail
			}
			errCode = errCode.WithDetails(err.Error())
		}
		response := app.NewResponse(ctx)
		response.ToErrorResponse(errCode)
	}
}
